/*******************************************************************************
 *            BASE CLASS
 ******************************************************************************/

/* Simple JavaScript Inheritance
  * By John Resig http://ejohn.org/
  * MIT Licensed.
  */
// Inspired by base2 and Prototype
(function(){
    var initializing = false, fnTest = /xyz/.test(function(){
        xyz;
    }) ? /\b_super\b/ : /.*/;


    // The base Class implementation (does nothing)
    this.Class = function(){};
   
    // Create a new Class that inherits from this class
    Class.extend = function(prop) {
        var _super = this.prototype;
     
        // Instantiate a base class (but only create the instance,
        // don't run the init constructor)
        initializing = true;
        var prototype = new this();
        initializing = false;
     
        // Copy the properties over onto the new prototype
        for (var name in prop) {
            // Check if we're overwriting an existing function
            prototype[name] = typeof prop[name] == "function" && 
            typeof _super[name] == "function" && fnTest.test(prop[name]) ?
            (function(name, fn){
                return function() {
                    var tmp = this._super;
             
                    // Add a new ._super() method that is the same method
                    // but on the super-class
                    this._super = _super[name];
             
                    // The method only need to be bound temporarily, so we
                    // remove it when we're done executing
                    var ret = fn.apply(this, arguments);        
                    this._super = tmp;
             
                    return ret;
                };
            })(name, prop[name]) :
            prop[name];
        }
     
        // The dummy class constructor
        function Class() {
            // All construction is actually done in the init method
            if ( !initializing && this.init )
                this.init.apply(this, arguments);
        }
     
        // Populate our constructed prototype object
        Class.prototype = prototype;
     
        // Enforce the constructor to be what we expect
        Class.prototype.constructor = Class;

        // And make this class extendable
        Class.extend = arguments.callee;
     
        return Class;
    };
})();

var Base = Class.extend(Base = {

    callbacks: {
        add: [],
        remove: []
    },
    
    /**
     * Registers a callback method based on the type
     * 
     * @param type the type of the callback, e.g. add
     * @param func the function to be used as the callback
     */
    registerCallback: function(type, func) {
        if( this.callbacks[type] == null ) {
            this.callbacks[type] = [];
        }
        
        this.callbacks[type].push(func);
    },
    
    /**
     * Fires all callbacks of a specified type and passes any other arguments 
     * to the callbacks for use
     */
    fireCallbacks: function() {
        var i = 0, j = 1, args = [], type = arguments[0];
        
        for(i = 0; i < arguments.length; i++, j++) {
            args[i] = arguments[j];
        }
        
        if( this.callbacks[type] ) {
            for(i = 0; i < this.callbacks[type].length; i++ ){
                this.callbacks[type][i].apply(this, args);
            }
        }
    }
});

/******************************************************************************
*            Model
/******************************************************************************/

var Model = Class.extend( Model =  {
    
    records : null,
    
    init: function() {
        this.records = {};
    },
    
    // adds a record.
    add : function(model, data) {
        if(typeof this.records[model] == "undefined"){ //First trasnsaction
            this.records[model] = [data];
        }
        
        else { // Subsequent records
            this.records[model].push(data);
        }
    },
    
    /**
     * Update a record or a record at a specified index
     * If only two parameters are specified, it is assumed the second parameter is
     * the data to be saved. This function will currently fail if the data parameter 
     * is not a valid JS Object.
     **/  
    update: function(model, index, data) {
        var key;
        
        if(arguments.length > 2){
            for(key in data){
                this.records[model][index][key] = data[key]; 
            }
        }
        else if(arguments.length > 1){
            data = index;
            for(key in data){
                this.records[model][key] = data[key]; 
            }
        }
    },
    
    updateAll: function(model, data){
        var model_data = this.get(model);
        
        for(var i = 0; i < model_data.length; i++) {
            for(var key in data){
                model_data[i][key] = data[key];
            }
        }
    },
    
    //Some models needs to have only one record item, so create wrapper and call this function in wrapper
    set: function(model, data) {
        if( arguments.length > 1 ) {
            this.records[model] = data;  
        }
    },
    
    // Get a record
    get: function(model, index) {
        if(arguments.length > 1){
            return this.records[model][index];
        }
        
        else if(arguments.length > 0){
            return this.records[model];
        }
        
        return null;
    },
    

    _delete: function(model, index){
        if(arguments.length > 1){
            this.records[model].splice(index, 1);
        }
        
        else if(arguments.length > 0){
            delete this.records[model];
        }
    },
    
    remove: function(model, index) {
        this._delete(model, index);
    },
    
    //Check if the data exsist in the array
    validate: function(arr, data){
        return (arr.indexOf(data) > -1);
    },
    
    
    //Format the Transactios for this Object
    format: function(){ //to be implemented later
        return this._cleanTmpIds();
    },

    _cleanTmpIds: function() {
        for(var k in this.records) {
            var value = this.records[k];

            if( value['id'] != null && value['id'].indexOf('tg') > -1 ) {
                delete value['id'];
            }
        }
    },

    getRecords: function() {
        return this.records;
    },
    
    /**
     * Iterates over the contents of a model list in the context of this Model object
     */
    foreach: function(model, callback) {
        var model_data = this.get(model);
        var self = this;
        
        if( !model_data ) {
            return;
        }
        
        $.each(model_data, function(key,value) {
            if( $.isFunction(callback) ) {
                return callback.apply(self, [key, value]); // allow possibility of loop break if callback returns false
            }
            
            return true;
        });
    }

});


/*******************************************************************************
 *            CONTROLLER
 ******************************************************************************/
var Controller = Base.extend( Controller =  {
    
    _view: null,
    _collection : {},
    _actions : {},
    
    init: function(collection) {
        if( collection ) {
            this._collection = collection;
        }

        else {
            this._collection = new Collection();
        }

        this._actions = new Collection();   
    },
    
    getCollection: function(){
        return this._collection;
    },
    
    setCollection: function(collection){
        this._collection = collection;
    },
    
    addAction: function(key, action_obj){
        this._actions.add(key, action_obj);
        
        if( action_obj && !action_obj.getController() ) {
            action_obj.setController(this);
        }
    },
    
    getAction: function(key){
        return this._actions.get(key);
    },
    
    setView: function(view) {
        this._view = view;
        this._view.setup(this);
    },
    
    getView: function() {
        return this._view;
    }
});

/*******************************************************************************
 *            VIEW
 ******************************************************************************/

var View = Base.extend( View = {
    
    _layout: null,
    _controller: null,
    
    /**
     * @return {Void} 
     */
    init: function(controller) {
        this._controller = controller;
    },
    
    setup: function(controller) {
        this.setController(controller);
    },
    
    /**
     * @param {Layout} layout layout for view
     */
    setLayout: function(layout) {
        this._layout = layout;
    },
    
    /**
     * @return {Layout} layout for view
     */
    getLayout: function() {
        return this._layout;
    },
    
    /**
     * @return {Controller} controller for view
     */
    getController: function(){
        return this._controller ;
    },
    
    /**
     * @param {Controller} controller controller for view
     */
    setController: function(controller){
        this._controller = controller;
    }
    
});

/******************************************************************************
*            Action class
/******************************************************************************/

var Action = Class.extend( Action = {
    
    _controller: null,
    _selector:null,
    
    init: function(controller) {
        if( !controller ) {
            console.error("No Controller Supplied To Action");
        }
        
        this.setController(controller);
    },
    
    getController: function(){
        return this._controller ;
    },
    
    setController: function(controller){
        this._controller = controller;
    },
    
    fire: function(){
       
    },
    
    bind:function(selector, type){
        var self = this;
        
        if( selector ) {
            self._selector = selector;
            $(selector).bind(type, function(e){
                e.preventDefault();
                self.fire();
            });  
        }  
    },
    
    url: function(params) {
        var url = $("#base_url").val();
        if( params.controller ) {
            url += params.controller;
        }
        
        if( params.action ) {
            url += "/" + params.action;
        }
        
        return url;
    }
});



/*******************************************************************************
 *            COLLECTIONS CLASS
 ******************************************************************************/
function Collection(data) {
    
    var list = {};
    var size = 0;
    var callbacks = {
        add: [],
        remove: []
    };
    
    /**
     * Adds a new object to the collection if the key for the object does not
     * already exist
     * 
     * @param key the collection key
     * @param value the value to add
     **/
    this.add = function(key,value) {
        if( typeof list[key] == 'undefined' ) {
            list[key] = value;
            size++;     
            
            this.fireCallbacks('add', key, value);                                                    
        }
    }
    
    /**
     * Removes the object identified by the key from the collection
     * 
     * @param key the search key
     **/
    this.remove = function(key) {
        if( typeof list[key] != 'undefined' ) {
            var value = list[key];
            delete list[key];
            size--;
            
            this.fireCallbacks('remove',key, value);
        }
    }
    
    this.registerCallback = function(type, callback){
        if(typeof callbacks[type] != 'undefined'){
            callbacks[type].push(callback);
        }
    }
    
    this.fireCallbacks = function(type, key, value){
        for(var i = 0; i < callbacks[type].length; i++ ){
            callbacks[type][i](key, value);
        }
    }
    
    /**
     * Returns the values stored for this key
     * 
     * @param key the search key
     **/
    this.get = function(key) {
        
        // if the a valid key is specified
        if( list.hasOwnProperty(key) ) {
            return list[key];
        }
        
        // if the key specified is a number, then we may be looking for an element
        // at a specified index
        if( !isNaN(parseInt(key)) ) {
            var index = 0;
            for(var k in list) {
                if( index == key ) {
                    return list[k];
                }
                
                index++;
            }
        }
        
        return null;
    }
    
    /**
     * Replaces or adds a new value to the map
     * 
     * @param key the key for the value
     * @param value the value to add
     */
    this.set = function(key,value) {
        list[key] = value;
    }
    
    /**
     * Returns the list of items stored in this collection
     * 
     * @return the list of items as an object
     */
    this.getList = function() {
        return list;
    };
    
    /**
     * Returns the count of the number items in this collection
     * 
     * @return number of items in this collection
     */
    this.getSize = function() {
        return size;
    };
    
    this.iterate = function(callback) {
        var collection = this;
        
        $.each(list, function(key, item) {
            if( $.isFunction(callback) ) {
                callback.apply(collection, [item]);
            }
        });
    };

    this.getMembersAsArray = function(member_guids) {
        var members = [];

        this.iterate(function(member) {
            if( member_guids != null ) {
                if( member_guids.indexOf( member.getGUID() ) > -1 ) {
                    members.push(member.get('Member'));
                }
            }
            
            else {
                members.push(member.get('Member'));
            }
        });

        return members;
    };

    this.getKeys = function() {
        return Object.keys(list);
    };
    
    this.removeAll = function() {
        var collection = this;
        
        $.each(list, function(key, item) {
            collection.remove(key);
        });
    };
    
    if( data ) {
        var _coll = this;
        $.each(data, function(k,v) {
            _coll.add(k, v);
        });
    }
}

(function( $ ){
    $.fn.serializeJSON=function() {
        var json = {};
        jQuery.map($(this).serializeArray(), function(n, i){
            json[n['name']] = n['value'];
        });
        return json;
    };
})( jQuery );